/*
 * $Id: Maybe.java,v 1.3 2007-11-13 19:31:33 klose Exp $
 */
package de.fanuffel.generic;

import java.util.List;

import de.fanuffel.generic.functions.Function;

public abstract class Maybe<Type> {

  private static <Type> Maybe<Type> just(Type t) {
    return new Just<Type>(t);
  }

  private static <Type> Maybe<Type> nothing() {
    return new Nothing<Type>();
  }

  public static class Just<Type> extends Maybe<Type> {
    final private Type value;

    public Just(final Type value) {
      this.value = value;
    }

    public boolean just(Variable<Type> v) {
      return v == null ? true : v.unify(new Variable<Type>(value));
    }

    public boolean isNothing() {
      return false;
    }

    public boolean isJust() {
      return true;
    }

    public Type fromJust() throws NoValueException {
      return value;
    }

    @Override public int hashCode() {
      return value.hashCode();
    }

    @Override public boolean equals(Object obj) {
      return value.equals(obj);
    }

    @Override public <B> B maybe(Function<Type, B> f, B b) {
      return f.eval(value);
    }
  }

  public static class Nothing<Type> extends Maybe<Type> {

    public boolean just(Variable<Type> v) {
      return false;
    }

    public boolean isNothing() {
      return true;
    }

    public boolean isJust() {
      return false;
    }

    public Type fromJust() throws NoValueException {
      throw new NoValueException();
    }

    @Override public int hashCode() {
      return super.hashCode();
    }

    @Override public boolean equals(Object obj) {
      return (obj instanceof Nothing);
    }

    @Override public <B> B maybe(Function<Type, B> f, B b) {
      return b;
    }
  }

  public abstract boolean just(final Variable<Type> v);

  public abstract Type fromJust() throws NoValueException;

  public abstract boolean isNothing();

  public abstract boolean isJust();

  public static <A, B, C> Function<A, Maybe<C>> concat(
      final Function<A, Maybe<B>> f, final Function<B, Maybe<C>> g) {
    return new Function<A, Maybe<C>>() {

      public Maybe<C> eval(A argument) {
        return combine(f.eval(argument), g);
      }

    };
  }

  public static <Type> Maybe<Type> lift(Type t) {
    if (t == null)
      return nothing();
    else
      return just(t);
  }

  public static <A> Function<A, Maybe<A>> combine(
      final List<Function<A, Maybe<A>>> funs) {
    return new Function<A, Maybe<A>>() {
      public Maybe<A> eval(A argument) {
        Maybe<A> a = lift(argument);
        for (Function<A, Maybe<A>> f : funs) {
          if (a.isNothing()) {
            return a;
          }
          a = a.apply(f);
        }
        return a;
      }
    };
  }

  public <B> Maybe<B> apply(Function<Type, Maybe<B>> f) {
    if (isNothing()) {
      return nothing();
    } else {
      return f.eval(fromJust());
    }
  }

  public static <A, B> Maybe<B> combine(Maybe<A> a, Function<A, Maybe<B>> f) {
    Variable<A> v = new Variable<A>();
    if (a.just(v)) {
      return f.eval(v.value());
    } else {
      return new Nothing<B>();
    }
  }

  public static class NoValueException extends RuntimeException {
    private static final long serialVersionUID = -517269560547397611L;
  }

  public static <X, Y> Maybe<X> cast(Class<X> x, Y y) {
    try {
      return new Maybe.Just<X>(x.cast(y));
    } catch (Exception e) {
      return new Maybe.Nothing<X>();
    }
  };

  public abstract <B> B maybe(Function<Type, B> f, B b);
}

class AllNoneEqual<A, B> implements Equal<Maybe.Nothing<A>, Maybe.Nothing<B>> {

  @SuppressWarnings("unchecked") public static Maybe.Nothing ex = new Maybe.Nothing();

  @SuppressWarnings("unchecked") public Maybe.Nothing<A> as(Maybe.Nothing<B> b) {
    return ex;
  }

  @SuppressWarnings("unchecked") public Maybe.Nothing<B> to(Maybe.Nothing<A> a) {
    return ex;
  }
};
